package com.carrot.mq.mqserver.core;

import com.carrot.mq.common.MqException;

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * @date 2023/8/22 22:11
 * 交换机的转发规则
 * 同时, 也可以验证 bindingKey routingKey 是否合法
 */
public class Router {

    /**
     * 验证 bindingKey 是否合法
     * @param bindingKey bindingKey
     * @return 合法 true
     */
    public boolean checkBindingKey (String bindingKey) {
        if (bindingKey.length() == 0) {
            // 直接交换机和扇出交换机的 bindingKey 可以为 ""
            return true;
        }
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            // 大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 下划线 _ 或者 .
            if (ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        // 检查 # 和 * 是否为独立的部分
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
            // 如果 word 长度大于 1 且包含 * 或者 # 就非法
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        // 通配符之间的相邻关系进行人为约定
        // aaa.#.#.bbb 非法 aaa.#.*.bbb 非法 aaa.*.#.bbb 非法 aaa.*.*.bbb 合法
        for (int i = 0; i < words.length - 1; i++) {
            // 连续两个 ##
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            // * #
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
            // # *
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
        }
        // 合法
        return true;
    }

    /**
     * 验证 routingKey 是否合法
     * @param routingKey routingKey
     * @return 合法 true
     */
    public boolean checkRoutingKey (String routingKey) {
        if (routingKey.length() == 0) {
            // 空字符串, 合法的情况. 比如在使用 fanout 交换机的时候, routingKey 就可以为 ""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 下划线 _ 或者 .
            if (ch == '_' || ch == '.') {
                continue;
            }
            // 不合法
            return false;
        }
        // 合法
        return true;
    }

    /**
     * 转发规则, 根据不同的 exchangeType 使用不同的转发规则
     * @param exchangeType 交换机类型
     * @param binding 绑定
     * @param message 消息
     * @return 可以转发 true
     */
    public boolean route (ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        if (exchangeType == ExchangeType.FANOUT) {
            // 扇出交换机, 该交换机绑定的所有队列都需要转发
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            // 主题交换机
            return routeTopic(binding, message);
        } else {
            // 直接交换机不需要调用该方法, 因此这种情况是不被允许的
            throw new MqException("[Router] 交换机类型非法! exchangeType=" + exchangeType);
        }
    }

    /**
     * 主题交换机的转发规则
     * 说明：bindingKey 由数字, 字母, 下划线组成, 并使用 . 分成多个部分。
     *                 创建绑定的时候给定的特殊字符串, 相当于出题。
     *                 支持两种特殊的符号作为通配符 * 和 # (必须是被 . 分成的独立的部分)
     *                      - 1) * 可以匹配任何一个独立的部分
     *                      - 2) # 可以匹配任何 0 个或者多个独立的部分
     *      routingKey 由数字, 字母, 下划线组成, 并使用 . 分成多个部分。
     *                 给消息上指定的特殊字符串, 相当于答案。
     * @param binding 绑定
     * @param message 消息
     * @return 可以转发 true
     */
    private boolean routeTopic(Binding binding, Message message) {
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");
        int bindingIndex = 0;
        int routingIndex = 0;
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            // 遇到 *
            if (bindingTokens[bindingIndex].equals("*")) {
                bindingIndex++;
                routingIndex++;
                continue;
            }
            // 遇到 #
            if (bindingTokens[bindingIndex].equals("#")) {
                bindingIndex++;
                // 如果已经到达末尾, 则匹配成功
                if (bindingIndex == bindingTokens.length) {
                    return true;
                }
                // # 后面还有内容, 则在 routingKey 中寻找对应的位置
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                if (routingIndex == -1) {
                    // 没找到
                    return false;
                }
                // 继续向后匹配
                bindingIndex++;
                routingIndex++;
            } else {
                // 普通字符, 则需要完全一样
                if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        // 判定是否同时到达末尾
        if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
            return true;
        }
        return false;
    }

    /**
     * 查找 bindingToken 在 routingTokens 对应的位置
     * @param routingTokens
     * @param routingIndex
     * @param bindingToken
     * @return 找到则返回对应的索引, 没找到则返回 -1
     */
    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if (routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }
}
